package com.tencent.iov.cowin.befsale.clue.crypt.config;

import cn.hutool.core.util.StrUtil;
import com.tencent.iov.cowin.befsale.clue.component.MaskComponent;
import com.tencent.iov.cowin.befsale.clue.crypt.annotation.CustomCryptField;
import com.tencent.cloud.iov.common.crypt.MyBatisParameterNameDiscoverer;
import org.apache.ibatis.binding.MapperMethod;
import org.apache.ibatis.cache.CacheKey;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.apache.ibatis.session.defaults.DefaultSqlSession;
import org.springframework.core.ParameterNameDiscoverer;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @Author: Caychen
 * @Date: 2022/2/16 14:52
 * @Description:
 */
@Intercepts(value = {
        @Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class}),
        @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class,
                RowBounds.class, ResultHandler.class}),
        @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class,
                RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class})})
public class CustomCryptInterceptor implements Interceptor {

    private MaskComponent maskComponent;

    /**
     * 参数注解缓存
     */
    private static final ConcurrentHashMap<String, Map<String, CustomCryptField>> PARAM_ANNOTATIONS_MAP = new ConcurrentHashMap<>();
    /**
     * 返回值注解缓存
     */
    private static final ConcurrentHashMap<String, CustomCryptField> RETURN_ANNOTATIONS_MAP = new ConcurrentHashMap<>();
    /**
     * 参数名解析器
     */
    private final ParameterNameDiscoverer parameterNameDiscoverer = new MyBatisParameterNameDiscoverer();

    public CustomCryptInterceptor(MaskComponent maskComponent) {
        this.maskComponent = maskComponent;
    }

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        Object[] args = invocation.getArgs();
        // 入参
        Object parameter = args[1];
        MappedStatement statement = (MappedStatement) args[0];

        //参数加密
        encryptOriginParamter(args, parameter, statement);

        // sql调用，获得出参
        Object returnValue = invocation.proceed();

        //原参数取消加密
        decryptOriginParameter(statement, parameter);

        // 出参解密
        if (isNotCrypt(returnValue)) {
            return returnValue;
        }

        // 获得方法注解(针对返回值)
        CustomCryptField customCryptField = getMethodAnnotations(statement);
        if (returnValue instanceof String) {
            return stringDecrypt((String) returnValue, customCryptField);
        }
        if (returnValue instanceof List) {
            listDecrypt((List) returnValue, customCryptField);
            return returnValue;
        }

        return returnValue;
    }

    /**
     * 参数加密
     *
     * @param args
     * @param parameter
     * @param statement
     * @throws ClassNotFoundException
     * @throws IllegalAccessException
     */
    private void encryptOriginParamter(Object[] args, Object parameter, MappedStatement statement) throws ClassNotFoundException, IllegalAccessException {
        // 判断是否需要解析
        if (!isNotCrypt(parameter)) {
            Map<String, CustomCryptField> cryptFieldMap = getParameterAnnotations(statement);
            // 单参数 string
            if (parameter instanceof String && !cryptFieldMap.isEmpty()) {
                args[1] = stringEncrypt(cryptFieldMap.keySet().iterator().next(), (String) parameter,
                        getParameterAnnotations(statement));
                // 单参数 list
            } else if (parameter instanceof DefaultSqlSession.StrictMap) {
                DefaultSqlSession.StrictMap<Object> strictMap = (DefaultSqlSession.StrictMap<Object>) parameter;
                for (Map.Entry<String, Object> entry : strictMap.entrySet()) {
                    if (entry.getKey().contains("collection")) {
                        continue;
                    }
                    if (entry.getKey().contains("list")) {
                        listEncrypt((List) entry.getValue(), cryptFieldMap.get(entry.getKey()));
                    }
                }
                // 多参数
            } else if (parameter instanceof MapperMethod.ParamMap) {
                MapperMethod.ParamMap<Object> paramMap = (MapperMethod.ParamMap<Object>) parameter;
                // 解析每一个参数
                for (Map.Entry<String, Object> entry : paramMap.entrySet()) {
                    // 判断不需要解析的类型 不解析map
                    if (isNotCrypt(entry.getValue()) || entry.getValue() instanceof Map
                            || entry.getKey().contains("param")) {
                        continue;
                    }
                    // 如果string
                    if (entry.getValue() instanceof String) {
                        entry.setValue(stringEncrypt(entry.getKey(), (String) entry.getValue(), cryptFieldMap));
                        continue;
                    }
                    // 如果 list
                    if (entry.getValue() instanceof List) {
                        listEncrypt((List) entry.getValue(), cryptFieldMap.get(entry.getKey()));
                        continue;
                    }
                    beanEncrypt(entry.getValue());
                }
                // bean
            } else {
                beanEncrypt(parameter);
            }
        }
    }

    /**
     * 对原始参数进行反解密
     *
     * @param statement
     * @param parameter
     * @throws ClassNotFoundException
     * @throws IllegalAccessException
     */
    private void decryptOriginParameter(MappedStatement statement, Object parameter) throws ClassNotFoundException, IllegalAccessException {
        // 判断是否需要解析
        if (!isNotCrypt(parameter)) {
            Map<String, CustomCryptField> cryptFieldMap = getParameterAnnotations(statement);
            // 单参数 string
            if (parameter instanceof String && !cryptFieldMap.isEmpty()) {
                parameter = stringDecrypt((String) parameter, cryptFieldMap.get(cryptFieldMap.keySet().iterator().next()));
                // 单参数 list
            } else if (parameter instanceof DefaultSqlSession.StrictMap) {
                DefaultSqlSession.StrictMap<Object> strictMap = (DefaultSqlSession.StrictMap<Object>) parameter;
                for (Map.Entry<String, Object> entry : strictMap.entrySet()) {
                    if (entry.getKey().contains("collection")) {
                        continue;
                    }
                    if (entry.getKey().contains("list")) {
                        listDecrypt((List) entry.getValue(), cryptFieldMap.get(entry.getKey()));
                    }
                }
                // 多参数
            } else if (parameter instanceof MapperMethod.ParamMap) {
                MapperMethod.ParamMap<Object> paramMap = (MapperMethod.ParamMap<Object>) parameter;
                // 解析每一个参数
                for (Map.Entry<String, Object> entry : paramMap.entrySet()) {
                    // 判断不需要解析的类型 不解析map
                    if (entry.getKey().contains("param") || isNotCrypt(entry.getValue()) || entry.getValue() instanceof Map) {
                        continue;
                    }
                    // 如果string
                    if (entry.getValue() instanceof String) {
                        entry.setValue(stringDecrypt((String) entry.getValue(), cryptFieldMap.get(entry.getKey())));
                        continue;
                    }
                    // 如果 list
                    if (entry.getValue() instanceof List) {
                        listDecrypt((List) entry.getValue(), cryptFieldMap.get(entry.getKey()));
                        continue;
                    }
                    beanDecrypt(entry.getValue());
                }
                // bean
            } else {
                beanDecrypt(parameter);
            }
        }
    }

    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    @Override
    public void setProperties(Properties properties) {

    }

    /**
     * 获取 方法上的注解
     *
     * @param statement MappedStatement
     * @return 方法上的加密注解 {@link CustomCryptField}
     * @throws ClassNotFoundException
     */
    private CustomCryptField getMethodAnnotations(MappedStatement statement) throws ClassNotFoundException {
        String id = statement.getId();

        CustomCryptField customCryptField = RETURN_ANNOTATIONS_MAP.get(id);
        if (customCryptField != null) {
            return customCryptField;
        }

        // 获取执行方法
        Method method = null;
        final Class clazz = Class.forName(id.substring(0, id.lastIndexOf(".")));
        for (Method _method : clazz.getDeclaredMethods()) {
            if (_method.getName().equals(id.substring(id.lastIndexOf(".") + 1))) {
                method = _method;
                break;
            }
        }
        if (method == null) {
            return null;
        }

        return method.getAnnotation(CustomCryptField.class);
    }

    /**
     * 获取 方法参数上的注解
     *
     * @param statement MappedStatement
     * @return 参数名与其对应加密注解
     * @throws ClassNotFoundException
     */
    private Map<String, CustomCryptField> getParameterAnnotations(MappedStatement statement) throws ClassNotFoundException {
        // 执行ID
        final String id = statement.getId();

        Map<String, CustomCryptField> cryptFieldMap = PARAM_ANNOTATIONS_MAP.get(id);
        if (cryptFieldMap != null) {
            return cryptFieldMap;
        } else {
            cryptFieldMap = new HashMap<>();
        }

        // 获取执行方法
        Method method = null;
        final Class clazz = Class.forName(id.substring(0, id.lastIndexOf(".")));
        for (Method _method : clazz.getDeclaredMethods()) {
            if (_method.getName().equals(id.substring(id.lastIndexOf(".") + 1))) {
                method = _method;
                break;
            }
        }
        if (method == null) {
            return cryptFieldMap;
        }

        // 获取参数名称
        String[] paramNames = parameterNameDiscoverer.getParameterNames(method);
        // 获取方法参数注解列表
        final Annotation[][] paramAnnotations = method.getParameterAnnotations();
        // 填充参数注解
        for (int i = 0; i < paramAnnotations.length; i++) {
            Annotation[] paramAnnotation = paramAnnotations[i];
            for (Annotation annotation : paramAnnotation) {
                if (annotation instanceof CustomCryptField) {
                    cryptFieldMap.put(paramNames[i], (CustomCryptField) annotation);
                    break;
                }
            }
        }

        // 存入缓存
        PARAM_ANNOTATIONS_MAP.put(id, cryptFieldMap);

        return cryptFieldMap;
    }

    /**
     * 判断是否需要加解密
     *
     * @param obj 待加密对象
     * @return 是否需要加密
     */
    private boolean isNotCrypt(Object obj) {
        return obj == null || obj instanceof Double || obj instanceof Integer || obj instanceof Long
                || obj instanceof Boolean;
    }

    /**
     * String 加密
     *
     * @param name             参数名称
     * @param plain            参数明文
     * @param paramAnnotations 加密注解
     * @return 密文
     */
    private String stringEncrypt(String name, String plain, Map<String, CustomCryptField> paramAnnotations) {
        return stringEncrypt(plain, paramAnnotations.get(name));
    }

    /**
     * String 加密
     *
     * @param plain            参数明文
     * @param customCryptField 加密注解
     * @return 密文
     */
    private String stringEncrypt(String plain, CustomCryptField customCryptField) {
        if (StrUtil.isBlank(plain) || customCryptField == null) {
            return plain;
        }

        return maskComponent.encryptAES128(plain);
    }

    /**
     * String 解密
     *
     * @param cipher           参数密文
     * @param customCryptField 加密注解
     * @return 明文
     */
    private String stringDecrypt(String cipher, CustomCryptField customCryptField) {
        if (StrUtil.isBlank(cipher) || customCryptField == null) {
            return cipher;
        }

        return maskComponent.decryptAES128(cipher);
    }

    /**
     * list 加密
     *
     * @param plainList        明文列表
     * @param customCryptField 加密方式注解
     * @return 密文列表
     * @throws IllegalAccessException
     */
    private List listEncrypt(List plainList, CustomCryptField customCryptField) throws IllegalAccessException {
        for (int i = 0; i < plainList.size(); i++) {
            Object plain = plainList.get(i);
            // 判断不需要解析的类型
            if (isNotCrypt(plain) || plain instanceof Map) {
                break;
            }
            if (plain instanceof String) {
                plainList.set(i, stringEncrypt((String) plain, customCryptField));
                continue;
            }
            beanEncrypt(plain);
        }

        return plainList;
    }

    /**
     * list 解密
     *
     * @param cipherList       密文列表
     * @param customCryptField 加密方式注解
     * @return 明文列表
     * @throws IllegalAccessException
     */
    private List listDecrypt(List cipherList, CustomCryptField customCryptField) throws IllegalAccessException {
        for (int i = 0; i < cipherList.size(); i++) {
            Object cipher = cipherList.get(i);
            // 判断不需要解析的类型
            if (isNotCrypt(cipher) || cipher instanceof Map) {
                break;
            }
            if (cipher instanceof String) {
                cipherList.set(i, stringDecrypt((String) cipher, customCryptField));
                continue;
            }
            beanDecrypt(cipher);
        }

        return cipherList;
    }

    /**
     * bean 加密
     *
     * @param plainObject 明文对象
     * @throws IllegalAccessException
     */
    private void beanEncrypt(Object plainObject) throws IllegalAccessException {
        Class objClazz = plainObject.getClass();
        Field[] objFields = objClazz.getDeclaredFields();
        for (Field field : objFields) {
            CustomCryptField customCryptField = field.getAnnotation(CustomCryptField.class);
            if (customCryptField != null) {
                field.setAccessible(true);
                Object plain = field.get(plainObject);
                if (plain == null) {
                    continue;
                }
                if (field.getType().equals(String.class)) {
                    field.set(plainObject, stringEncrypt((String) plain, customCryptField));
                    continue;
                }
                if (field.getType().equals(List.class)) {
                    field.set(plainObject, listEncrypt((List) plain, customCryptField));
                    continue;
                }
                field.setAccessible(false);
            }
        }
    }

    /**
     * bean 解密
     *
     * @param cipherObject 密文对象
     * @throws IllegalAccessException
     */
    private void beanDecrypt(Object cipherObject) throws IllegalAccessException {
        Class objClazz = cipherObject.getClass();
        Field[] objFields = objClazz.getDeclaredFields();
        for (Field field : objFields) {
            CustomCryptField customCryptField = field.getAnnotation(CustomCryptField.class);
            if (customCryptField != null) {
                field.setAccessible(true);
                Object cipher = field.get(cipherObject);
                if (cipher == null) {
                    continue;
                }
                if (field.getType().equals(String.class)) {
                    field.set(cipherObject, stringDecrypt((String) cipher, customCryptField));
                    continue;
                }
                if (field.getType().equals(List.class)) {
                    field.set(cipherObject, listDecrypt((List) cipher, customCryptField));
                    continue;
                }
            }
        }
    }
}